package com.icesoft.base.manager.security.config;

import com.icesoft.base.manager.security.impl.DefaultAccessDeniedHandler;
import com.icesoft.base.manager.security.login.handle.ResourceAuthExceptionEntryPoint;
import com.icesoft.base.manager.security.login.handle.UnifiedAuthFailureHandler;
import com.icesoft.base.manager.security.login.handle.UnifiedAuthSuccessHandler;
import com.icesoft.base.manager.security.login.password.SimpleHashPasswordEncode;
import com.icesoft.base.manager.security.login.password.UsernamePasswordDecryptAuthenticationFilter;
import com.icesoft.base.manager.security.login.phone.PhoneAuthenticationFilter;
import com.icesoft.base.manager.security.login.phone.PhoneAuthenticationProvider;
import com.icesoft.base.manager.security.suppose.IAccessRule;
import com.icesoft.base.manager.security.suppose.IUserDetailsService;
import com.icesoft.core.common.helper.BeanHold;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UnifiedAuthFailureHandler failureHandler;
    @Autowired
    private UnifiedAuthSuccessHandler successHandler;
    @Autowired
    private SecurityProperty securityProperty;

    @Override
    public void configure(WebSecurity web) {
        if (StringUtils.isNotBlank(securityProperty.getDefaultIgnoreUrls())) {
            for (String ignoreURL : securityProperty.getDefaultIgnoreUrls().trim().split(",")) {
                log.info("默认开放请求路径：{}", ignoreURL);
                web.ignoring().antMatchers(ignoreURL.trim());
            }
        }

        if (StringUtils.isNotBlank(securityProperty.getIgnoreUrls())) {
            for (String ignoreURL : securityProperty.getIgnoreUrls().trim().split(",")) {
                log.info("自定义开放请求路径：{}", ignoreURL);
                web.ignoring().antMatchers(ignoreURL.trim());
            }
        }
        web.ignoring().antMatchers(HttpMethod.GET, "/**/*.ico", "/**/*.png", "/**/*.gif", "/**/*.jpg", "/**/*.svg"
                , "/error/*", "/error", "/401", "/403", "/404", "/500", "/lib/**",
                "/**/*.eot", "/**/*.ttf", "/**/*.woff", "/**/*.woff2", "/fonts/**", "/font/**", "/**/*.css", "/**/*.js");
    }

    @Autowired(required = false)
    private Set<IAccessRule> accessRules;

    @Autowired
    private SessionRegistry sessionRegistry;

    public static final String[] authenticatedUrls = new String[]{"/system/common/**", "/system/index.html"};

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        if (accessRules != null) {
            for (IAccessRule accessRole : accessRules) {
                String className = accessRole.getClass().getSimpleName();
                String beanName = className.substring(0, 1).toLowerCase() + className.substring(1);
                http.authorizeRequests().antMatchers(accessRole.urlPattern())
                        .access("@" + beanName + ".hasPermission(request, authentication)");
            }
        }
        http.authorizeRequests().antMatchers(securityProperty.getLoginPage()).permitAll();
        http.authorizeRequests().antMatchers(authenticatedUrls).authenticated();
        http.formLogin().loginPage(securityProperty.getLoginPage())
                .loginProcessingUrl(SecurityConst.LOGIN_PROCESS_API)
                .failureHandler(failureHandler)
                .successHandler(successHandler)
                .and().logout()
                .logoutUrl(SecurityConst.LOGOUT_API)
                .invalidateHttpSession(true)
                .logoutSuccessUrl(securityProperty.getLoginPage())
                .and().sessionManagement()
                .maximumSessions(-1)
                .expiredUrl(SecurityConst.LOGOUT_API)
                .sessionRegistry(sessionRegistry);
        if (securityProperty.isSingleLogin()) {
            http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(false);
        }

        http.authorizeRequests()
                // 设置监控端请求角色
                .requestMatchers(EndpointRequest.toAnyEndpoint())
                .access("@rbacService.hasEndpointPermission(request, authentication) or @rbacService.hasPermission(request, authentication)")
                // 对其他所有请求进行过滤操作
                .anyRequest()
                .access("authenticated and @rbacService.hasPermission(request, authentication)");

        http.headers().frameOptions().disable().and().csrf().disable();
        http.addFilterAt(phoneAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        http.addFilterAt(decryptAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
        if (StringUtils.isNotBlank(securityProperty.getCorsUrl())) {
            http.cors();
        }
        http.exceptionHandling()
                .accessDeniedHandler(new DefaultAccessDeniedHandler())
                .authenticationEntryPoint(new ResourceAuthExceptionEntryPoint(securityProperty));
    }

    /**
     * 默认使用DefaultAuthenticationEventPublisher
     * {@link org.springframework.security.authentication.DefaultAuthenticationEventPublisher}
     */
    @Autowired
    private AuthenticationEventPublisher authenticationEventPublisher;
    @Autowired
    private PhoneAuthenticationProvider phoneAuthenticationProvider;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // super.configure(auth);
        auth.authenticationProvider(phoneAuthenticationProvider);
        auth.authenticationProvider(daoAuthenticationProvider());
        auth.authenticationEventPublisher(authenticationEventPublisher);
    }

    /**
     * 自定义的Filter.
     */
    @Bean
    public UsernamePasswordDecryptAuthenticationFilter decryptAuthenticationProcessingFilter() throws Exception {
        UsernamePasswordDecryptAuthenticationFilter filter = new UsernamePasswordDecryptAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager());
        filter.setAuthenticationSuccessHandler(successHandler);
        filter.setAuthenticationFailureHandler(failureHandler);
        return filter;
    }

    @Bean
    public PhoneAuthenticationFilter phoneAuthenticationFilter() throws Exception {
        PhoneAuthenticationFilter filter = new PhoneAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager());
        filter.setAuthenticationSuccessHandler(successHandler);
        filter.setAuthenticationFailureHandler(failureHandler);
        return filter;
    }

    private DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        daoAuthenticationProvider.setUserDetailsService(BeanHold.getBean(IUserDetailsService.class));
        return daoAuthenticationProvider;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        String encodingId = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put(encodingId, new BCryptPasswordEncoder());
        DelegatingPasswordEncoder delegatingPasswordEncoder = new DelegatingPasswordEncoder(encodingId, encoders);
        delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(new SimpleHashPasswordEncode());
        return delegatingPasswordEncoder;
    }
}